package org.dromara.llm.llm.factory;

import com.google.cloud.vertexai.VertexAI;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.dromara.llm.domain.SysLlmConfig;
import org.dromara.llm.llm.api.DateTimeTools;
import org.dromara.llm.mapper.SysLlmConfigMapper;
import org.springframework.ai.chat.model.ChatModel;
import org.springframework.ai.deepseek.DeepSeekChatModel;
import org.springframework.ai.deepseek.DeepSeekChatOptions;
import org.springframework.ai.deepseek.api.DeepSeekApi;
import org.springframework.ai.minimax.MiniMaxChatModel;
import org.springframework.ai.minimax.MiniMaxChatOptions;
import org.springframework.ai.minimax.api.MiniMaxApi;
import org.springframework.ai.model.NoopApiKey;
import org.springframework.ai.model.SimpleApiKey;
import org.springframework.ai.ollama.OllamaChatModel;
import org.springframework.ai.ollama.api.OllamaApi;
import org.springframework.ai.ollama.api.OllamaOptions;
import org.springframework.ai.openai.OpenAiChatModel;
import org.springframework.ai.openai.OpenAiChatOptions;
import org.springframework.ai.openai.api.OpenAiApi;
import org.springframework.ai.retry.RetryUtils;
import org.springframework.ai.support.ToolCallbacks;
import org.springframework.ai.tool.ToolCallback;
import org.springframework.ai.vertexai.gemini.VertexAiGeminiChatModel;
import org.springframework.ai.vertexai.gemini.VertexAiGeminiChatOptions;
import org.springframework.ai.zhipuai.ZhiPuAiChatModel;
import org.springframework.ai.zhipuai.ZhiPuAiChatOptions;
import org.springframework.ai.zhipuai.api.ZhiPuAiApi;
import org.springframework.stereotype.Component;
import org.springframework.util.Assert;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.util.StringUtils;

import java.util.List;

/**
 * ChatModel工厂
 * 根据配置的模型ID，创建对应的ChatModel
 */
@Component
@AllArgsConstructor
@Slf4j
public class ChatModelFactory {
    private SysLlmConfigMapper configService;
    private DateTimeTools dateTimeTools;

    /**
     * 根据配置ID创建ChatModel，首次调用时缓存，缓存key为配置ID。
     *
     * @param configId 配置ID，实际是模型ID。
     * @return ChatModel
     */
    public ChatModel takeChatModel(Integer configId) {
        Assert.notNull(configId, "配置ID不能为空");
        // 根据配置ID查询配置
        SysLlmConfig config = configService.selectById(configId);
        return createChatModel(config);
    }

    /**
     * 创建ChatModel
     * 根据系统配置信息选择合适的聊天模型提供商，并使用这些配置信息初始化对应的聊天模型
     * @param config 系统配置对象，包含聊天模型提供商、模型名称、API URL、API密钥、应用ID和API密钥等信息
     * @return 根据配置信息创建的ChatModel实例
     */
    public ChatModel createChatModel(SysLlmConfig config) {
        log.info("config:{}", config);
        // 获取并转换提供商名称为小写，以确保一致性
        String provider = config.getProvider().toLowerCase();
        // 直接获取模型名称，无需转换
        String model = config.getConfigName();
        // 获取API的URL
        String endpoint = config.getApiUrl();
        // 获取API密钥
        String apiKey = config.getApiKey();
        // 获取应用ID
        String appId = config.getAppId();
        // 获取API密钥，注意这里变量重复了，可能是误写，应为apiSecret
        String apiSecret = config.getApiSecret();
        // 再次转换提供商名称为小写，这一步可能是多余的，因为前面已经转换过了
        provider = provider.toLowerCase();
        // 根据不同的提供商创建并返回对应的ChatModel实例
        return switch (provider) {
            case "ollama" ->
                // 创建并返回Ollama聊天模型
                newOllamaChatModel(endpoint, appId, apiKey, apiSecret, model);
            case "zhipuai" ->
                // 创建并返回智谱聊天模型
                newZhipuChatModel(endpoint, appId, apiKey, apiSecret, model);
            case "deepseek" ->
                // 创建并返回DeepSeek聊天模型
                newDeepSeekChatModel(endpoint, appId, apiKey, apiSecret, model);
            case "minimax" ->
                // 创建并返回Minimax聊天模型
                newMinimaxChatModel(endpoint, appId, apiKey, apiSecret, model);
            case "gemini" ->
                // 创建并返回Gemini聊天模型
                newGimniChatModel(endpoint, appId, apiKey, apiSecret, model);
            case "openai" ->
                // 创建并返回OpenAI聊天模型
                newOpenAiChatModel(endpoint, appId, apiKey, apiSecret, model);
            default ->
                // 创建并返回OpenAI聊天模型，作为默认选项
                newOpenAiChatModel(endpoint, appId, apiKey, apiSecret, model);
        };
    }


    /**
     * 创建一个新的Ollama聊天模型实例
     *
     * @param endpoint  API服务的基地址
     * @param appId     应用的唯一标识符
     * @param apiKey    应用的API密钥
     * @param apiSecret API的密钥密钥
     * @param model     使用的模型名称
     * @return 返回构建的ChatModel实例
     */
    private ChatModel newOllamaChatModel(String endpoint, String appId, String apiKey, String apiSecret, String model) {
        // 构建OllamaApi实例，设置API的基地址
        var ollamaApi = OllamaApi.builder().baseUrl(endpoint).build();
        // 构建OllamaChatModel实例，设置OllamaApi和默认的模型选项
        var chatModel = OllamaChatModel.builder()
            .ollamaApi(ollamaApi)
            .defaultOptions(
                OllamaOptions.builder()
                    .model(model)
                    .build())
            .build();
        // 记录使用的模型名称
        log.info("Using Ollama model: {}", model);
        return chatModel;
    }


    private ChatModel newOpenAiChatModel(String endpoint, String appId, String apiKey, String apiSecret, String model) {
        MultiValueMap<String, String> headers = new LinkedMultiValueMap<>();
        headers.add("Content-Type", "application/json");

        var openAiApi = OpenAiApi.builder()
            .apiKey(StringUtils.hasText(apiKey) ? new SimpleApiKey(apiKey) : new NoopApiKey())
            .baseUrl(endpoint)
            .completionsPath("/chat/completions")
            .headers(headers)
            .build();
        var openAiChatOptions = OpenAiChatOptions.builder()
            .model(model)
            .temperature(0.7)
            .build();

        var chatModel = OpenAiChatModel.builder()
            .openAiApi(openAiApi)
            .defaultOptions(openAiChatOptions)
            .build();
        log.info("Using OpenAi model: {}, endpoint:{}", model, endpoint);
        return chatModel;
    }

    /**
     * 创建一个新的智谱聊天模型实例
     *
     * @param endpoint  服务端点URL
     * @param appId     应用ID
     * @param apiKey    API密钥
     * @param apiSecret API密钥密钥
     * @param model     模型名称
     * @return 返回一个初始化后的ChatModel对象
     */
    private ChatModel newZhipuChatModel(String endpoint, String appId, String apiKey, String apiSecret, String model) {
        ToolCallback[] dateTimeTools = ToolCallbacks.from(new DateTimeTools());

        // 创建智谱AI API实例，用于与服务端进行通信
        var zhiPuAiApi = new ZhiPuAiApi(endpoint, apiKey);

        var chatOptions = ZhiPuAiChatOptions.builder()
            .model(model)
            .toolCallbacks(dateTimeTools)
            .build();
        // 创建并配置智谱AI聊天模型实例
        var chatModel = new ZhiPuAiChatModel(zhiPuAiApi, chatOptions);

        // 记录使用的模型信息
        log.info("Using zhiPu model: {}", model);

        // 返回初始化后的聊天模型实例
        return chatModel;
    }

    /**
     * 创建 DeepSeekChatModel 实例
     *
     * @param endpoint  DeepSeek API 的基础地址
     * @param appId     应用ID（可选）
     * @param apiKey    API 密钥
     * @param apiSecret API 秘钥（如果需要，否则可以省略）
     * @param model     模型名称，例如 "deepseek-chat"
     * @return 初始化好的 ChatModel 实例
     */
    private ChatModel newDeepSeekChatModel(String endpoint, String appId, String apiKey, String apiSecret, String model) {
        MultiValueMap<String, String> headers = new LinkedMultiValueMap<>();
        headers.add("Content-Type", "application/json");
        var deepSeekApi = DeepSeekApi.builder()
            .apiKey(StringUtils.hasText(apiKey) ? new SimpleApiKey(apiKey) : new NoopApiKey())
            .baseUrl(endpoint)
            .completionsPath("/chat/completions")
            .headers(headers)
            .build();

        var chatOptions = DeepSeekChatOptions.builder()
            .model(model)
            .build();

        var chatModel = DeepSeekChatModel.builder()
            .deepSeekApi(deepSeekApi)
            .defaultOptions(chatOptions)
            .build();
        log.info("Using DeepSeek model: {}", model);
        return chatModel;
    }

    /**
     * 创建一个新的Minimax聊天模型实例
     *
     * @param endpoint  API服务的基地址
     * @param appId     应用的唯一标识符
     * @param apiKey    应用的API密钥
     * @param apiSecret API的密钥密钥
     * @param model     使用的模型名称
     * @return 返回构建的ChatModel实例
     */
    private ChatModel newMinimaxChatModel(String endpoint, String appId, String apiKey, String apiSecret, String model) {
        // 构建MiniMaxApi实例，设置API的基地址,apiSecret 为miniMaxToken
        var miniMaxApi = new MiniMaxApi(endpoint, apiSecret);

        var chatOptions = MiniMaxChatOptions.builder()
            .model(model)
            .frequencyPenalty(0.0)
            .maxTokens(2048)
            .presencePenalty(0.0)
            .stop(List.of("this-is-the-end"))
            .temperature(0.7)
            .topP(1.0)
            .build();

        // 构建MiniMaxChatModel实例，设置MiniMaxApi和默认的模型选项
        var chatModel = new MiniMaxChatModel(miniMaxApi, chatOptions);                        // 构建ChatModel实例
        // 记录使用的模型名称
        log.info("Using Minimax model: {}", model);
        return chatModel;       // 返回构建的ChatModel实例
    }

    /**
     * 创建一个新的Gimni聊天模型实例
     *
     * @param endpoint  API服务的基地址
     * @param appId     应用的唯一标识符
     * @param apiKey    应用的API密钥
     * @param apiSecret API的密钥密钥
     * @param model     使用的模型名称
     * @return 构建的ChatModel实例
     */
    public ChatModel newGimniChatModel(String endpoint, String appId, String apiKey, String apiSecret, String model) {
        //TODO 参数和其他不一样，需要projectid
        var vertexAI = new VertexAI("", endpoint);
        var options = VertexAiGeminiChatOptions.builder()
            .temperature(0.7)
            .topP(1.0)
            .model(VertexAiGeminiChatModel.ChatModel.GEMINI_2_0_FLASH.getValue())
            .build();

        var chatModel = new VertexAiGeminiChatModel(vertexAI,
            options,
            null,
            RetryUtils.SHORT_RETRY_TEMPLATE, null);

        // 记录使用的模型名称
        log.info("Using Gimni model: {}", model);
        return chatModel;       // 返回构建的ChatModel实例

    }
}
